package com.jspx.security.asymmetric.impl;

import com.jspx.boot.environment.Environment;
import com.jspx.security.asymmetric.AbstractEncrypt;
import com.jspx.security.KeyPairGen;
import com.jspx.security.sm.*;
import com.jspx.security.utils.EncryptUtil;
import com.jspx.utils.ArrayUtil;
import com.jspx.utils.StringUtil;
import org.bouncycastle.asn1.*;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Enumeration;

/**
 * SM2椭圆曲线公钥密码算法
 * SM2算法：SM2椭圆曲线公钥密码算法是我国自主设计的公钥密码算法，包括SM2-1椭圆曲线数字签名算法，SM2-2椭圆曲线密钥交换协议，
 * SM2-3椭圆曲线公钥加密算法，分别用于实现数字签名密钥协商和数据加密等功能。SM2算法与RSA算法不同的是，
 * SM2算法是基于椭圆曲线上点群离散对数难题，相对于RSA算法，256位的SM2密码强度已经比2048位的RSA密码强度要高。
 *
 * 每种加密算法的保持格式会有不同SM2保存为Hex ,16进制字符串
 */
public class SM2Encrypt extends AbstractEncrypt {
    private String userId = "1234567812345678";
    private static final SM2 sm2 = new SM2();

    public KeyPairGen getKeyPair()
    {
        AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.generateKeyPair();
        ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
        ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
        BigInteger privateKey = ecpriv.getD();
        ECPoint publicKey = ecpub.getQ();
        return new KeyPairGen("SM2",publicKey.getEncoded(),privateKey.toByteArray());
    }

    public  void setUserId(String userId) {
        this.userId = userId;
    }

    /**
     *  使用默认id计算
     * @param sourceData 数据
     * @param publicKey 共密
     * @param signData  验证
     * @return
     * @throws Exception
     */
    public boolean verify( byte[] sourceData,byte[] publicKey, byte[] signData) throws Exception
    {
        return verify(userId.getBytes(Environment.defaultEncode), sourceData,publicKey, signData);
    }


    @SuppressWarnings("unchecked")
    public boolean verify(byte[] userId, byte[] sourceData,byte[] publicKey,  byte[] signData) {
        if (ArrayUtil.isEmpty(publicKey)||ArrayUtil.isEmpty(signData)||ArrayUtil.isEmpty(sourceData)) {
            return false;
        }
        byte[] formatPubKey;
        if (publicKey.length == 64) {
            //添加一字节标识，用于ECPoint解析
            formatPubKey = new byte[65];
            formatPubKey[0] = 0x04;
            System.arraycopy(publicKey, 0, formatPubKey, 1, publicKey.length);
        } else
            formatPubKey = publicKey;

        ECPoint userKey = sm2.ecc_curve.decodePoint(formatPubKey);

        SM3Digest sm3 = new SM3Digest();
        byte[] z = sm2.sm2GetZ(userId, userKey);
        sm3.update(z, 0, z.length);
        sm3.update(sourceData, 0, sourceData.length);
        byte[] md = new byte[32];
        sm3.doFinal(md, 0);

        ByteArrayInputStream bis = new ByteArrayInputStream(signData);
        ASN1InputStream dis = new ASN1InputStream(bis);
        SM2Result sm2Result = null;
        try {

            ASN1Primitive derObj = dis.readObject();
            Enumeration<ASN1Integer> e = ((ASN1Sequence) derObj).getObjects();
            BigInteger r =  e.nextElement().getValue();
            BigInteger s =  e.nextElement().getValue();
            sm2Result = new SM2Result();
            sm2Result.r = r;
            sm2Result.s = s;
            sm2.verify(md, userKey, sm2Result.r, sm2Result.s, sm2Result);
            return sm2Result.r.equals(sm2Result.R);
        } catch (IOException e1) {
            e1.printStackTrace();
            return false;
        }
    }

    /**
     *
     * @param sourceData  数据
     * @param privateKey  私密
     * @return
     */
    public byte[] sign(byte[] sourceData,byte[]  privateKey) {
        try {
            return sign(userId.getBytes(Environment.defaultEncode), sourceData,privateKey);
        } catch (Exception e) {
            e.printStackTrace();
            return StringUtil.empty.getBytes();
        }
    }

    public byte[] sign(byte[] userId, byte[] sourceData,byte[] privateKey) throws Exception
    {
        if (ArrayUtil.isEmpty(privateKey)||ArrayUtil.isEmpty(sourceData)) {
            return null;
        }
        //这里不能使用BigInteger 直接转换，位数太大会变成负数
        BigInteger userD = EncryptUtil.byteConvertInteger(privateKey);
        ECPoint userKey = sm2.ecc_point_g.multiply(userD);
        SM3Digest sm3 = new SM3Digest();
        byte[] z = sm2.sm2GetZ(userId, userKey);
        sm3.update(z, 0, z.length);
        sm3.update(sourceData, 0, sourceData.length);
        byte[] md = new byte[32];
        sm3.doFinal(md, 0);
        SM2Result sm2Result = new SM2Result();
        sm2.sign(md, userD, userKey, sm2Result);
        ASN1Integer d_r = new ASN1Integer(sm2Result.r);
        ASN1Integer d_s = new ASN1Integer(sm2Result.s);
        ASN1EncodableVector v2 = new ASN1EncodableVector();
        v2.add(d_r);
        v2.add(d_s);
        DERSequence derObject = new DERSequence(v2);
        return derObject.getEncoded();

    }


    public byte[] encryptByPublicKey(byte[] data, byte[] publicKey)
    {
        if (ArrayUtil.isEmpty(publicKey)||ArrayUtil.isEmpty(data)) {
            return null;
        }
        byte[] source = new byte[data.length];
        System.arraycopy(data, 0, source, 0, data.length);
        byte[] formatPubKey;
        if (publicKey.length == 64) {
            //添加一字节标识，用于ECPoint解析
            formatPubKey = new byte[65];
            formatPubKey[0] = 0x04;
            System.arraycopy(publicKey, 0, formatPubKey, 1, publicKey.length);
        } else
            formatPubKey = publicKey;

        Cipher cipher = new Cipher();
        ECPoint userKey = sm2.ecc_curve.decodePoint(formatPubKey);
        ECPoint c1 = cipher.init_enc(sm2, userKey);
        cipher.encrypt(source);
        byte[] c3 = new byte[32];
        cipher.doFinal(c3);
        ASN1Integer x = new ASN1Integer(c1.getX().toBigInteger());
        ASN1Integer y = new ASN1Integer(c1.getY().toBigInteger());
        DEROctetString derDig = new DEROctetString(c3);
        DEROctetString derEnc = new DEROctetString(source);
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(x);
        v.add(y);
        v.add(derDig);
        v.add(derEnc);
        DERSequence seq = new DERSequence(v);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DEROutputStream dos = new DEROutputStream(bos);
        try {
            dos.writeObject(seq);
            return bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public byte[] decryptByPrivateKey(byte[] encryptedData, byte[] privateKey)
    {
        if (ArrayUtil.isEmpty(encryptedData)||ArrayUtil.isEmpty(privateKey)) {
            return null;
        }

        //加密字节数组转换为十六进制的字符串 长度变为encryptedData.length * 2
       // String data = Util.byteToHex(encryptedData);
        /***分解加密字串
         * （C1 = C1标志位2位 + C1实体部分128位 = 130）
         * （C3 = C3实体部分64位  = 64）
         * （C2 = encryptedData.length * 2 - C1长度  - C2长度）
         */

        byte[] enc = new byte[encryptedData.length];
        System.arraycopy(encryptedData, 0, enc, 0, encryptedData.length);
        BigInteger userD = EncryptUtil.byteConvertInteger(privateKey);// new BigInteger(1, privateKey);
        ByteArrayInputStream bis = new ByteArrayInputStream(enc);
        ASN1InputStream dis = new ASN1InputStream(bis);
        try {
            ASN1Primitive derObj = dis.readObject();
            ASN1Sequence asn1 = (ASN1Sequence) derObj;
            ASN1Integer x = (ASN1Integer) asn1.getObjectAt(0);
            ASN1Integer y = (ASN1Integer) asn1.getObjectAt(1);
            ECPoint c1 = sm2.ecc_curve.createPoint(x.getValue(), y.getValue(), true);
            Cipher cipher = new Cipher();
            cipher.initDec(userD, c1);
            DEROctetString data = (DEROctetString) asn1.getObjectAt(3);
            enc = data.getOctets();
            cipher.decrypt(enc);
            byte[] c3 = new byte[32];
            cipher.doFinal(c3);
            return enc;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * sm2没有实现
     * @param data
     * @param publicKey
     * @return
     * @throws Exception
     */
    @Override
    public byte[] decryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {

        return decryptByPrivateKey(data, publicKey);
    }

    /**
     * m2没有实现
     * @param data
     * @param privateKey
     * @return
     * @throws Exception
     */
    @Override
    public byte[] encryptByPrivateKey(byte[] data, byte[] privateKey) throws Exception {
        return encryptByPublicKey(data,privateKey);
    }

}
